package wal

import (
	"bytes"
	"encoding/binary"
	"encoding/json"
	"fmt"
	"log"
	"lsmtree/kv"
	sorttree "lsmtree/sortTree"
	"os"
	"path"
	"sync"
	"time"
)

/*

在 LSM-Tree（Log-Structured Merge-Tree）中，WAL 是 Write-Ahead Log 的缩写。WAL 是一种用于确保数据一致性和持久性的技术，特别是在数据库系统和存储引擎中广泛使用。
Write-Ahead Log (WAL)
WAL 的基本思想是，在对数据进行任何修改之前，先将这些修改记录到一个持久化的日志中。这种日志通常是顺序写入的，写入速度非常快。具体来说，WAL 的工作机制如下：
记录日志：当有数据需要修改时，首先将这些修改记录到 WAL 中。这些日志条目包含了必要的信息，以便在系统崩溃后能够重做这些修改。
执行修改：在日志记录完成后，再将实际的修改应用到内存中的数据结构（如 MemTable）。
持久化数据：根据需要，将内存中的数据批量写入到持久化存储（如 SSTable）中。
LSM-Tree 中的 WAL
在 LSM-Tree 中，WAL 的作用尤为重要，因为 LSM-Tree 的设计依赖于将大量的小写操作合并成大块的顺序写操作，以提高写入性能。具体来说，LSM-Tree 中的 WAL 具有以下作用：
数据恢复：在系统崩溃或故障时，WAL 可以用于恢复未持久化的数据。通过重放 WAL 中的日志条目，可以将内存中的数据恢复到崩溃前的状态。
数据一致性：WAL 确保了数据修改的顺序性和一致性。在将数据写入内存（MemTable）之前，先记录到 WAL 中，确保即使在系统崩溃时，也能按照正确的顺序重做这些修改。
高效写入：由于 WAL 是顺序写入的，因此写入速度非常快。这使得 LSM-Tree 能够高效地处理大量写操作，而不需要立即将每次写操作都持久化到磁盘。

*/

type Wal struct {
	f    *os.File
	path string
	lock sync.Locker
}

func (w *Wal) Init(dir string) {
	log.Println("Loading wal.log...")
	start := time.Now()
	defer func() {
		elapse := time.Since(start)
		log.Println("Loading wal.log done, elapsed time: ", elapse)
	}()
	uuidStr := time.Now().Format("2015-09-03-15-05-04")
	walPath := path.Join(dir, fmt.Sprintf("%s_wal.log", uuidStr))
	log.Printf("init wal.log: walPath: %s\n", walPath)
	f, err := os.OpenFile(walPath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
	if err != nil {
		log.Fatal("Failed to open wal.log: ", err)
		panic(err)
	}

	w.f = f
	w.path = walPath
	w.lock = &sync.Mutex{}
}

func (w *Wal) LoadFromFile(path string, tree *sorttree.Tree) *sorttree.Tree {
	f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
	if err != nil {
		log.Fatal("Failed to open wal.log: ", err)
		panic(err)
	}
	w.f = f
	w.path = path
	w.lock = &sync.Mutex{}
	return w.LoadToMemory(tree)
}

// loadToMemory 会返回一个具有所有节点的 Tree，并把所有节点数据加载到参数的 tree 中
// 通过 wal.log文件初始化 Wal，加载文件中的 WalF 的内存中
func (w *Wal) LoadToMemory(tree *sorttree.Tree) *sorttree.Tree {
	w.lock.Lock()
	defer w.lock.Unlock()

	preTree := &sorttree.Tree{}
	preTree.Init()
	info, _ := os.Stat(w.path)
	size := info.Size()

	// 空的 wal.log
	if size == 0 {
		return preTree
	}

	_, err := w.f.Seek(0, 0)
	if err != nil {
		log.Println("Failed to seek wal.log: ", err)
		panic(err)
	}

	defer func(f *os.File, offset int64, whence int) {
		_, err := f.Seek(offset, whence)
		if err != nil {
			log.Println("Failed to seek wal.log: ", err)
			panic(err)
		}
	}(w.f, size-1, 0)

	// 将文件内容加载到内存中
	data := make([]byte, size)
	_, err = w.f.Read(data)
	if err != nil {
		log.Println("Failed to read wal.log: ", err)
		panic(err)
	}

	dataLen := int64(0) // 元素的字节数量
	index := int64(0)   // 当前索引

	for index < size {
		// 前八个字节表示元素长度
		indexData := data[index:(index + 8)]
		// 获取元素字节长度
		buf := bytes.NewBuffer(indexData)
		err := binary.Read(buf, binary.LittleEndian, &dataLen)
		if err != nil {
			log.Println("Failed to read wal.log")
			panic(err)
		}
		index += 8
		dataArea := data[index:(index + dataLen)]
		var value kv.Value
		err = json.Unmarshal(dataArea, &value)

		if err != nil {
			log.Println("Failed to unmarshal value: ", err)
			panic(err)
		}

		if value.Deleted {
			tree.Delete(value.Key)
			preTree.Delete(value.Key)
		} else {
			tree.Set(value.Key, value.Value)
			preTree.Set(value.Key, value.Value)
		}

		// 读取下一个元素
		index = index + dataLen
	}
	return preTree
}

// 记录日志
func (w *Wal) Write(value kv.Value) {
	w.lock.Lock()
	defer w.lock.Unlock()

	if value.Deleted {
		log.Println("wal.log: delete ", value.Key)
	} else {
		log.Println("wal.log: write ", value.Key)
	}

	data, _ := json.Marshal(value)
	// 前八个字节写长度
	err := binary.Write(w.f, binary.LittleEndian, int64(len(data)))
	if err != nil {
		log.Println("Failed to write wal.log: ", err)
		panic(err)
	}

	// 接下来的 dataLen 字节写数据
	err = binary.Write(w.f, binary.LittleEndian, data)
	if err != nil {
		log.Println("Failed to write wal.log: ", err)
		panic(err)
	}
}

func (w *Wal) Reset() {
	w.lock.Lock()
	defer w.lock.Unlock()

	log.Println("Reseting the wal.log file")

	err := w.f.Close()
	if err != nil {
		log.Println("Failed to close wal.log: ", err)
		panic(err)
	}

	w.f = nil
	err = os.Remove(w.path)
	if err != nil {
		log.Println("Failed to remove wal.log: ", err)
		panic(err)
	}

	f, err := os.OpenFile(w.path, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
	if err != nil {
		log.Fatal("Failed to open wal.log: ", err)
		panic(err)
	}
	w.f = f
}

func (w *Wal) DeleteFile() {
	w.lock.Lock()
	defer w.lock.Unlock()
	err := w.f.Close()
	if err != nil {
		log.Println("Failed to close wal.log: ", err)
		panic(err)
	}

	err = os.Remove(w.path)
	if err != nil {
		log.Println("Failed to remove wal.log: ", err)
		panic(err)
	}
}
